package com.smsc.headend.task.engine.utils;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.HexUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONUtil;
import com.smsc.headend.module.data.dto.AnsiSubDataItemDTO;
import com.smsc.headend.module.data.dto.DataItemDTO;
import com.smsc.headend.module.protocol.ansi.AnsiByteBuffer;
import com.smsc.headend.module.protocol.cosem.util.ByteNumberUtils;
import com.smsc.headend.module.protocol.cosem.util.ByteUtils;
import com.smsc.headend.module.template.tou.entity.TemplateTouSeasonEntity;
import com.smsc.headend.task.engine.po.TouItem;
import lombok.extern.slf4j.Slf4j;

import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Slf4j
public class AnsiUtil {

    public static byte[] formatPacketReadPartTableByOffset(int tableId, int offset, int count) {
        byte buffer[] = {0x3F, 0, 0, 0, 0, 0, 0, 0};
        buffer[1] = (byte) ((tableId >>> 8) & 0xFF);
        buffer[2] = (byte) (tableId & 0xFF);
        buffer[3] = (byte) ((offset >>> 16) & 0xFF);
        buffer[4] = (byte) ((offset >>> 8) & 0xFF);
        buffer[5] = (byte) (offset & 0xFF);
        buffer[6] = (byte) ((count >>> 8) & 0xFF);
        buffer[7] = (byte) (count & 0xFF);

        return buffer;
    }

    public static byte[] formatPacketReadFullTable(int tableId) {
        byte buffer[] = {0x30, 0, 0};
        buffer[1] = (byte) ((tableId >>> 8) & 0xFF);
        buffer[2] = (byte) (tableId & 0xFF);

        return buffer;
    }

    public static byte[] formatPacketWritePartTableByOffset(int tableId, int offset, byte[] data, int len) {
        byte buffer[] = new byte[8 + len + 1];
        buffer[0] = 0x4F;
        buffer[1] = (byte) ((tableId >>> 8) & 0xFF);
        buffer[2] = (byte) (tableId & 0xFF);
        buffer[3] = (byte) ((offset >>> 16) & 0xFF);
        buffer[4] = (byte) ((offset >>> 8) & 0xFF);
        buffer[5] = (byte) (offset & 0xFF);
        buffer[6] = (byte) ((len >>> 8) & 0xFF);
        buffer[7] = (byte) (len & 0xFF);

        int cs = 0;
        for (int i = 0; i < len; ++i) {
            buffer[8 + i] = data[i];
            cs += data[i];
        }
        buffer[8 + len] = (byte) (0x100 - cs & 0xFF);

        return buffer;
    }

    public static byte[] formatPacketWriteFullTable(int tableId, byte[] data, int len) {
        byte buffer[] = new byte[5 + len + 1];
        buffer[0] = 0x40;
        buffer[1] = (byte) ((tableId >>> 8) & 0xFF);
        buffer[2] = (byte) (tableId & 0xFF);
        buffer[3] = (byte) ((len >>> 8) & 0xFF);
        buffer[4] = (byte) (len & 0xFF);

        int cs = 0;
        for (int i = 0; i < len; ++i) {
            buffer[5 + i] = data[i];
            cs += data[i];
        }
        buffer[5 + len] = (byte) (0x100 - cs & 0xFF);

        return buffer;
    }

    /**
     * 报文不带校验位
     *
     * @param tableId
     * @param data
     * @param len
     * @return byte[]
     */
    public static byte[] formatPacketWithoutCrc(int tableId, byte[] data, int len) {
        byte[] buffer = new byte[4 + len + 1];
        buffer[0] = 0x40;
        buffer[1] = (byte) ((tableId >>> 8) & 0xFF);
        buffer[2] = (byte) (tableId & 0xFF);
        buffer[3] = (byte) ((len >>> 8) & 0xFF);
        buffer[4] = (byte) (len & 0xFF);

        int cs = 0;
        for (int i = 0; i < len; ++i) {
            buffer[5 + i] = data[i];
            cs += data[i];
        }

        return buffer;
    }

    public static byte[] formatPacketWithProcedureId(int tableId, boolean isSTD, int procedureId) {
        byte selector = 0, seq_nbr = 0;
        int lenDataBuf = 3;
        byte[] dataBuf = new byte[lenDataBuf];
        // TABLE_IDB_BFLD 2bytes
        dataBuf[0] = (byte) (((selector & 0xF) << 4) | ((isSTD ? 0 : 1) << 3) | ((procedureId >>> 8) & 0x7));
        dataBuf[1] = (byte) (procedureId & 0xFF);
        // SEQ_NBR 1byte
        dataBuf[2] = seq_nbr;

        return formatPacketWriteFullTable(tableId, dataBuf, lenDataBuf);
    }

    public static byte[] formatConnectDisconnectPacket(int tableId, int procedureId) {
        int lenDataBuf = 5;
        byte[] dataBuf = new byte[lenDataBuf];
        dataBuf[0] = 0x08;
        dataBuf[1] = 0x00;
        dataBuf[2] = 0x00;
        dataBuf[3] = 0x00;
        dataBuf[4] = (byte) (procedureId << 1);

        return formatPacketWritePartTableByOffset(tableId, 0, dataBuf, lenDataBuf);
    }

    public static byte[] formatDatetimePacket(Long modelId, String zoneOffsetId, Date date) {
        boolean isDST = true;
        byte data[] = new byte[11];
        int len = 0;
        // TODO modelId 特殊处理
//        if (modelId == DeviceModel.PM120_V3_0) {
//            // 反过来特殊处理
//            data[len++] = 0x0A; // SEQ_NBR （0）
//            data[len++] = 0x00; // Procedure number （10）
//            data[len++] = 0x00;
//        } else {
        data[len++] = 0x00;
        data[len++] = 0x0A; // Procedure number （10）
        data[len++] = 0x00; // SEQ_NBR （0）
//        }
        data[len++] = 0x07;
        LocalDateTime dateTime = DateUtil.toLocalDateTime(date);

//        tt = (lastRecvTime - lastSendTime) / 1000;
//        odt = odt.plusSeconds(tt);
        log.debug("Meter's time change to {}", dateTime);

        data[len++] = (byte) ((dateTime.getYear() - 2000) & 0xFF);
        data[len++] = (byte) (dateTime.getMonthValue());
        data[len++] = (byte) (dateTime.getDayOfMonth());
        data[len++] = (byte) (dateTime.getHour());
        data[len++] = (byte) (dateTime.getMinute());
        data[len++] = (byte) (dateTime.getSecond());
        data[len++] = (byte) ((dateTime.getDayOfWeek().getValue() - 1) | (isDST ? 1 << 3 : 0));

        return AnsiUtil.formatPacketWriteFullTable(7, data, len);
    }

    public static byte[] formatLpDataSource(AnsiSubDataItemDTO ansiSubDataItemDTO, int intFmtCde1, List<DataItemDTO> channels) {
        int size = channels.size();
        // int totalSize = size * 3 + 1 + size * 2 + size * 2;
        AnsiByteBuffer lpSelSet1 = new AnsiByteBuffer(size * 3);
        AnsiByteBuffer scalarsSet1 = new AnsiByteBuffer(size * 2);
        AnsiByteBuffer divisorsSet1 = new AnsiByteBuffer(size * 2);
        channels.stream().forEach(dataItem -> {
            AnsiSubDataItemDTO ansiSubDataItem = (AnsiSubDataItemDTO) dataItem;
            lpSelSet1.setUInt8(ansiSubDataItem.getEndRdgFlag());
            lpSelSet1.setUInt8(ansiSubDataItem.getEndBlkRdgSourceSelect());
            lpSelSet1.setUInt8(ansiSubDataItem.getLpSourceSelect());

            scalarsSet1.setUInt16(ansiSubDataItem.getDsScalar());
            divisorsSet1.setUInt16(ansiSubDataItem.getDivisor());
        });

        byte[] data = ArrayUtil.addAll(lpSelSet1.getData(), new byte[]{(byte) intFmtCde1}, scalarsSet1.getData(), divisorsSet1.getData());

        return formatPacketWriteFullTable(ansiSubDataItemDTO.getTableId(), data, data.length);
    }

    public static byte[] formatProcedure(AnsiSubDataItemDTO ansiSubDataItemDTO) {
        byte selector = 0, seq_nbr = 0;
        byte[] data;
        if (ansiSubDataItemDTO.getUnit() == null) {
            data = new byte[3];
            // TABLE_IDB_BFLD 2bytes
            data[0] = (byte) (((selector & 0xF) << 4) | (0 << 3) | ((ansiSubDataItemDTO.getProcedureId() >>> 8) & 0x7));
            data[1] = (byte) (ansiSubDataItemDTO.getProcedureId() & 0xFF);
            // SEQ_NBR 1byte
            data[2] = seq_nbr;
        } else {
            data = new byte[4];
            data[0] = 0x00;    // TABLE_IDB_BFLD 2bytes
            data[1] = 0x04;
            data[2] = 0x00;    // SEQ_NBR 1byte
            data[3] = (byte) (ansiSubDataItemDTO.getUnit() & 0xFF);
        }

        return formatPacketWriteFullTable(ansiSubDataItemDTO.getTableId(), data, data.length);
    }

    public static void setFormatRDate(String dt, AnsiByteBuffer dataBuffer) {
        dt = dt.replaceAll("XXXX", "65535");
        dt = dt.replaceAll("FFFF", "65535");
        dt = dt.replaceAll("FF", "255");
        dt = dt.replaceAll("FE", "254");
        dt = dt.replaceAll("FD", "253");
        dt = dt.replaceAll(" ", "-");
        dt = dt.replaceAll(":", "-");

        String[] strList = dt.split("-");
        byte month = (byte) (Integer.parseInt(strList[1]) & 0xFF);
        byte day = (byte) (Integer.parseInt(strList[2]) & 0xFF);

        dataBuffer.setUInt8(day << 3);
        dataBuffer.setUInt8(month);
    }

    public static byte[] formatTouSeason(List<TemplateTouSeasonEntity> seasonProfileList) {
        int size = seasonProfileList.size();
        AnsiByteBuffer abb = new AnsiByteBuffer((size + 1) * 3);
        for (TemplateTouSeasonEntity season : seasonProfileList) {
            // season_start
            setFormatRDate(season.getStartTime(), abb);
            // season_id
            abb.setUInt8((season.getSeasonId().intValue() & 0xFF) + 3);
        }
        // 列表结束
        abb.setUInt8(0x08);
        abb.setUInt8(0x01); // 随便设置一个日期（元旦）
        abb.setUInt8(31);   // 列表结束
        return AnsiUtil.formatPacketWritePartTableByOffset(54, 24, abb.getData(), abb.size());
    }

    public static byte[] formatTouWeek(List<TouItem> touItems) {
        AnsiByteBuffer abb = new AnsiByteBuffer(4 * 4 * 3);
        Map<Integer, List<TouItem>> listMap = touItems.stream().distinct().sorted(Comparator.comparing(TouItem::getDayId)).collect(Collectors.groupingBy(TouItem::getDayId));
        for (Map.Entry<Integer, List<TouItem>> entry : listMap.entrySet()) {
            List<TouItem> dayTouList = entry.getValue();
            fillUpListTo4(dayTouList);
            for (TouItem touItem : dayTouList) {
                abb.set(touItem.toBytes());
            }
        }

        return AnsiUtil.formatPacketWritePartTableByOffset(54, 120, abb.getData(), abb.size());
    }

    public static void fillUpListTo4(List<TouItem> dayTouList) {
        int size = dayTouList.size();
        if (size < 4) {
            TouItem touItem = dayTouList.get(size - 1);
            for (; size < 4; ++size) {
                TouItem fillUpItem = ObjectUtil.clone(touItem);
                dayTouList.add(fillUpItem);
            }
        }
    }

    public static String parseDateTimeString(AnsiByteBuffer dataBuffer) {
        byte[] data = dataBuffer.getData();
        int lenParse = 0;
        if (data[lenParse] != 0 || dataBuffer.size() < (1 + 2 + 9 + 1))
            return "waiting";
        lenParse = lenParse + 3;

        int year = data[lenParse + 0] + 2000;
        int month = data[lenParse + 1];
        int day = data[lenParse + 2];
        int hour = data[lenParse + 3];
        int min = data[lenParse + 4];
        int sec = data[lenParse + 5];
        lenParse += 6;

        String value = "";
        if (year != 0xFF && month != 0xFF && day != 0xFF && hour != 0xFF && min != 0xFF && sec != 0xFF) {
            boolean isDST = ((data[lenParse] >>> 6) & 0x01) != 0 && ((data[lenParse] >>> 3) & 0x01) != 0;
            // TODO 获取meter的timezone 转换时间
            LocalDateTime odt = LocalDateTime.of(year, month, day, hour, min, sec);
            DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
            value = fmt.format(odt);
        } else {
            value = "Invalid data";
        }

        return value;
    }

    public static Integer parseConnectDisconnect(AnsiByteBuffer dataBuffer) {
        int lenParse = 0;
        byte[] data = dataBuffer.getData();
        if (data[lenParse] != 0 || data.length < 7)
            return null;

        Integer statusCode;
        if ((data[3] & 0X1C) == 0x04) {
            statusCode = 0;
        } else {
            statusCode = 1;
        }

        return statusCode;
    }

    public static List<Integer[]> parseValue4ByteByIndex(AnsiByteBuffer dataBuffer, Integer intervalSize, Integer nbrChnsSet1, boolean isReverse) {
        int index = 3;
        List<Integer[]> list = CollUtil.newArrayList();
        for (Integer j = 0; j < intervalSize; j++) {
            Integer[] data = new Integer[nbrChnsSet1];
            for (Integer i = 0; i < nbrChnsSet1; i++) {
                byte[] bytes = dataBuffer.subArray(index, 4);
                if (isReverse) {
                    bytes = ArrayUtil.reverse(bytes);
                }
                data[i] = Integer.parseUnsignedInt(HexUtil.encodeHexStr(bytes), 16);
                index += 4;
            }
            list.add(data);
        }

        return list;
    }

    public static Integer parseValue4Byte(AnsiByteBuffer dataBuffer, boolean isReverse) {
        byte[] bytes = dataBuffer.subArray(3, 4);
        if (isReverse) {
            bytes = ArrayUtil.reverse(bytes);
        }
        return Integer.parseUnsignedInt(HexUtil.encodeHexStr(bytes), 16);
    }

    public static Short parseValue2Byte(AnsiByteBuffer dataBuffer, boolean isReverse) {
        byte[] bytes = dataBuffer.subArray(3, 2);
        if (isReverse) {
            bytes = ArrayUtil.reverse(bytes);
        }
        return ByteNumberUtils.byte2ToShort(bytes);
    }

    public static String parseValueString(AnsiByteBuffer dataBuffer) {
        if (dataBuffer.size() <= 3) {
            return null;
        }
        byte[] data = dataBuffer.subArray(3, dataBuffer.size() - 4);
        return new String(data, StandardCharsets.UTF_8).trim();
    }

    public static List<AnsiSubDataItemDTO> parseLpDataSource(int size, AnsiByteBuffer dataBuffer) {
        List<AnsiSubDataItemDTO> list = CollUtil.newArrayList();
        dataBuffer.position(3);
        for (int i = 0; i < size; i++) {
            AnsiSubDataItemDTO dataItem = new AnsiSubDataItemDTO();
            dataItem.setEndRdgFlag(dataBuffer.getUInt8() & 0xFF);
            dataItem.setEndBlkRdgSourceSelect(dataBuffer.getUInt8() & 0xFF);
            dataItem.setLpSourceSelect(dataBuffer.getUInt8() & 0xFF);
            list.add(dataItem);
        }

        return list;
    }

    public static String[] parseTouSeason(AnsiByteBuffer ansiByteBuffer) {
        String[] seasonStart = new String[4];
        byte[] dataBuffer = ansiByteBuffer.getData();
        int index = 3;
        for (int i = 0; i < 16; i++, index += 3) {
            int ctrl = (dataBuffer[index + 2] & 0x1F);
            if (ctrl == 31) {
                break;
            }
            if (ctrl < 3 || 6 < ctrl) {
                continue;
            }
            int season_id = ctrl - 3;
            int month = dataBuffer[index + 1] & 0x0F;
            int day = (dataBuffer[index] >> 3) & 0x1F;
            seasonStart[season_id] = genDateTimeStr(month, day);
        }

        return seasonStart;
    }

    public static Map<Integer, List<TouItem>> parseTouWeek(AnsiByteBuffer ansiByteBuffer) {
        List<TouItem> touItemList = CollUtil.newArrayList();
        byte[] dataBuffer = ansiByteBuffer.getData();
        int index = 3;
        for (int i = 0; i < 16; ++i, index += 3) {
            TouItem ti = new TouItem(dataBuffer, index);
            touItemList.add(ti);
        }
        System.out.println(JSONUtil.toJsonStr(touItemList.stream().distinct().collect(Collectors.toList())));
        return touItemList.stream().distinct().collect(Collectors.groupingBy(TouItem::getDayId));
    }

    public static String genDateTimeStr(int month, int day) {
        int year = 0xFFFF;
        month = month & 0xff;
        day = day & 0xff;
        int hour = 0;
        int minute = 0;
        int second = 0;

        String timeValue = String.format("%04d-%02d-%0" +
                "" +
                "2d %02d:%02d:%02d ", year, month, day, hour, minute, second);

        timeValue = timeValue.replaceAll("65535", "FFFF");
        timeValue = timeValue.replaceAll("255", "FF");
        timeValue = timeValue.replaceAll("254", "FE");
        timeValue = timeValue.replaceAll("253", "FD");
        return timeValue;
    }
}
